package com.un.ebs.workflow.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.un.ebs.contract.dto.ContractVo;
import com.un.ebs.contract.service.ContractService;
import com.un.ebs.core.UserContext;
import com.un.ebs.core.exception.BusinessEntityNotFoundException;
import com.un.ebs.core.exception.BusinessException;
import com.un.ebs.core.util.SecurityUtils;
import com.un.ebs.core.util.StringUtils;
import com.un.ebs.inventory.enums.InventoryTypeEnum;
import com.un.ebs.inventory.service.InventoryNoteService;
import com.un.ebs.sys.service.ConfigureService;
import com.un.ebs.sys.service.UserGroupService;
import com.un.ebs.sys.service.UserService;
import com.un.ebs.sys.service.WxService;
import com.un.ebs.workflow.CostCode;
import com.un.ebs.workflow.domain.*;
import com.un.ebs.workflow.dto.WorkflowGatewayVo;
import com.un.ebs.workflow.dto.WorkflowNodeVo;
import com.un.ebs.workflow.enums.NodePassTypeEnum;
import com.un.ebs.workflow.event.WorkflowInstancePushEvent;
import com.un.ebs.workflow.mapper.WorkflowInstanceMapper;
import com.un.ebs.workflow.mapper.WorkflowNodeInstanceExtMapper;
import com.un.ebs.workflow.mapper.WorkflowNodeInstanceMapper;
import com.un.ebs.workflow.service.WorkflowInstanceService;
import com.un.ebs.workflow.service.WorkflowService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author liubo
 * @email liubocs@163.com
 * @date 2020/8/23 18:11
 */
@Service
public class WorkflowInstanceServiceImpl extends ServiceImpl<WorkflowInstanceMapper, WorkflowInstance> implements WorkflowInstanceService {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource
    private WorkflowService workflowService;

    @Resource
    private WorkflowInstanceMapper workflowInstanceMapper;

    @Resource
    private WorkflowNodeInstanceMapper workflowNodeInstanceMapper;

    @Resource
    private WorkflowNodeInstanceExtMapper workflowNodeInstanceExtMapper;

    @Resource
    private UserGroupService userGroupService;

    @Resource
    private UserService userService;

    @Resource
    private ConfigureService configureService;

    @Resource
    private WxService wxService;

    @Transactional
    @Override
    public String start(String workflowId, String remark) {
        Workflow wf = workflowService.queryById(workflowId);
        if (wf == null) {
            throw new BusinessEntityNotFoundException(workflowId, "流程");
        }

        WorkflowInstance wfInstance = new WorkflowInstance();
        wfInstance.setWorkflowId(wf.getId());
        wfInstance.setWorkflowName(wf.getName());
        wfInstance.setRemark(remark);
        wfInstance.insert();

        WorkflowNodeGateway gateway = workflowService.queryFirstGateway(workflowId);
        createTask(wfInstance.getId(), gateway.getNextNodeId(), null, remark);

        return wfInstance.getId();
    }

    @Override
    public String start(String workflowId, InventoryTypeEnum type, String noteId, String noteCode) {
        Workflow wf = workflowService.queryById(workflowId);
        if (wf == null) {
            throw new BusinessEntityNotFoundException(workflowId, "流程");
        }

        WorkflowInstance wfInstance = new WorkflowInstance();
        wfInstance.setWorkflowId(wf.getId());
        wfInstance.setWorkflowName(wf.getName());
        wfInstance.setInventoryType(type);
        wfInstance.setInventoryNoteId(noteId);
        wfInstance.setInventoryNoteCode(noteCode);
        wfInstance.insert();

        WorkflowNodeGateway gateway = workflowService.queryFirstGateway(workflowId);
        if (gateway == null) {
            throw new BusinessException(String.format("没有找到【%s】流程的第一个网关。", wf.getName()));
        }
        createTask(wfInstance.getId(), gateway.getNextNodeId(), null, null);

        return wfInstance.getId();
    }

    @Override
    public String createTask(String workflowInstanceId, String nodeId, String parentId, String remark) {
        WorkflowNodeExt node = workflowService.queryNodeById(nodeId);
        if (node == null) {
            throw new BusinessEntityNotFoundException(nodeId, "流程节点");
        }
        WorkflowInstance workflowInstance = workflowInstanceMapper.selectByIdNotTenant(workflowInstanceId);
        if (workflowInstance == null) {
            throw new BusinessEntityNotFoundException(workflowInstanceId, "流程实例");
        }
        if (workflowInstance.getCloseStatus()) {
            throw new BusinessException("流程已结束，不能创建后续任务。");
        }

//        if(!SecurityUtils.getTenantId().equals(workflowInstance.getTenantId())){
//            checkTenant(SecurityUtils.getUserContent().getId(),workflowInstance.getTenantId());
//        }

//        WorkflowNodeInstance nodeInstance = new WorkflowNodeInstance();
//        nodeInstance.setWorkflowInstanceId(workflowInstanceId);
//        nodeInstance.setNodeId(nodeId);
//        nodeInstance.setNodePassType(NodePassTypeEnum.Default);
//        nodeInstance.setParentId(parentId);
//        nodeInstance.setCreateRemark(remark);
//        nodeInstance.setTenantId(workflowInstance.getTenantId());
//        nodeInstance.insert();
        String id = UUID.randomUUID().toString();
        baseMapper.createTask(id, workflowInstanceId, nodeId, NodePassTypeEnum.Default, parentId, SecurityUtils.getUserContent().getId(), SecurityUtils.getUserContent().getPersonnelName(), workflowInstance.getTenantId());
        sendNotifyByUserGroup(node.getUserGroupId(), queryTaskById(id));

        return id;
    }

    private void checkTenant(String userId, String tenantId) {
        UserContext userContext = userService.markUserContext(userId, tenantId, null);
        if (userContext == null) {
            throw new BusinessException("切换租户时，没有找到用户所在租户的信息");
//            userContext = userService.markUserContext(userId, tenantId, null);
//            if (userContext == null) {
//                logger.warn(String.format("撤销过期订单时，没有一个有效的租户【%s】管理员账号可用。", obj.getTenantId()));
//                continue;
//            }
        }
        SecurityUtils.setUserContent(userContext);
    }

    @Transactional
    @Override
    public void taskPush(String nodeInstanceId, String remark, NodePassTypeEnum type) {
        //WorkflowNodeInstance nodeInstance = workflowNodeInstanceMapper.selectById(nodeInstanceId);
        WorkflowNodeInstance nodeInstance = baseMapper.selectNodeByIdNotTenant(nodeInstanceId);
        if (nodeInstance == null) {
            throw new BusinessEntityNotFoundException(nodeInstanceId, "流程节点任务");
        }
        if (nodeInstance.getNodePassType() != NodePassTypeEnum.Default) {
            throw new BusinessException("流程任务已完成，不能重复操作。");
        }

        WorkflowInstance workflowInstance = baseMapper.selectByIdNotTenant(nodeInstance.getWorkflowInstanceId());
        if (workflowInstance.getCloseStatus()) {
            throw new BusinessException("流程已结束，不能创建后续任务。");
        }
        if (type == null) {
            throw new BusinessException("请指定状态。");
        }
        if (type == NodePassTypeEnum.Default) {
            throw new BusinessException("状态要么是通过，要么是不通过。");
        }
        if (StringUtils.isNullOrEmpty(remark) && type != NodePassTypeEnum.Pass_Yes) {
            throw new BusinessException("请填写不通过原因。");
        }
        baseMapper.completeTask(nodeInstanceId, type, SecurityUtils.getUserContent().getId(), SecurityUtils.getUserContent().getPersonnelName(), remark);

        if (type != NodePassTypeEnum.Pass_Yes) {
            close(nodeInstance.getWorkflowInstanceId(), remark, false);
        }

        WorkflowNodeExt currNode = workflowService.queryNodeById(nodeInstance.getNodeId());
        List<WorkflowNodeVo> nextNodes = workflowService.queryNextNodes(currNode.getWorkflowId(), currNode.getId());
        if (StringUtils.isNullOrEmpty(nextNodes)) {
            close(nodeInstance.getWorkflowInstanceId(), "正常结束。", type == NodePassTypeEnum.Pass_Yes);
            publishEvent(nodeInstance.getId(), true, type == NodePassTypeEnum.Pass_Yes);
        } else {
            for (WorkflowNodeVo next : nextNodes) {
                if (StringUtils.isNullOrEmpty(next.getNextGateway())) {

                    // 流程结束
                    close(nodeInstance.getWorkflowInstanceId(), "正常结束。", type == NodePassTypeEnum.Pass_Yes);

                    // 发布事件
                    publishEvent(nodeInstance.getId(), true, type == NodePassTypeEnum.Pass_Yes);

                    // 通知实例的发起人
                    sendCloseNotify(workflowInstance.getCreateAt(), nodeInstance.getWorkflowInstanceId(), type == NodePassTypeEnum.Pass_Yes);
                    return;
                } else {
                    for (WorkflowGatewayVo gateway : next.getNextGateway()) {
                        String id = createTask(nodeInstance.getWorkflowInstanceId(), gateway.getNextNodeId(), nodeInstance.getId(), null);
                        publishEvent(id, false, type == NodePassTypeEnum.Pass_Yes);
                    }
                }
            }
        }

    }

    private void publishEvent(String nodeInstanceId, boolean isClose, boolean isPass) {
        WorkflowNodeInstanceExt nodeExt = baseMapper.selectNodeExtByIdNotTenant(nodeInstanceId);
        if (nodeExt == null) {
            throw new BusinessException("系统异常，刚创建的流程节点实例ID无效。");
        }
        WorkflowInstancePushEvent.publishEvent(this, nodeExt.getWorkflowInstanceId(), nodeInstanceId, nodeExt.getInventoryNoteId(), nodeExt.getInventoryType(), isClose, isPass);
    }

    @Override
    public void close(String instanceId, String remark, boolean isPass) {
        WorkflowInstance workflowInstance = baseMapper.selectByIdNotTenant(instanceId);
        if (workflowInstance == null) {
            throw new BusinessEntityNotFoundException(instanceId, "流程实例");
        }

        if (workflowInstance.getCloseStatus()) {
            return;
        }

        baseMapper.closeWorkflowInstance(instanceId, SecurityUtils.getUserContent().getId(), SecurityUtils.getUserContent().getPersonnelName(), remark, isPass);
    }

    @Override
    public IPage<WorkflowInstance> query(String workflowId, int pageSize, int pageIndex) {
        Page<WorkflowInstance> page = new Page<>(pageIndex, pageSize);
        LambdaQueryWrapper<WorkflowInstance> lq = Wrappers.lambdaQuery();
        lq.eq(WorkflowInstance::getWorkflowId, workflowId);
        lq.orderByDesc(WorkflowInstance::getCreateTime);
        return baseMapper.selectPage(page, lq);
    }

    @Override
    public List<WorkflowNodeInstanceExt> queryTask() {
        List<String> groupIds = userGroupService.queryByUserId(SecurityUtils.getUserContent().getId()).stream().map(s -> s.getId()).collect(Collectors.toList());
        if (StringUtils.isNullOrEmpty(groupIds)) {
            return new ArrayList<>();
        }
        LambdaQueryWrapper<WorkflowNodeInstanceExt> lq = Wrappers.lambdaQuery();
        lq.in(WorkflowNodeInstanceExt::getUserGroupId, groupIds);
        lq.orderByAsc(WorkflowNodeInstanceExt::getCreateTime);
        return workflowNodeInstanceExtMapper.selectList(lq);
    }

    @Override
    public List<WorkflowNodeInstanceExt> queryTask(String workflowInstanceId) {
        LambdaQueryWrapper<WorkflowNodeInstanceExt> lq = Wrappers.lambdaQuery();
        lq.eq(WorkflowNodeInstanceExt::getWorkflowInstanceId, workflowInstanceId);
        lq.orderByAsc(WorkflowNodeInstanceExt::getCreateTime);
        return workflowNodeInstanceExtMapper.selectList(lq);
    }

    @Override
    public List<WorkflowNodeInstanceExt> queryTask(String workflowInstanceId, String userId) {
        List<String> groupIds = userGroupService.queryByUserId(userId).stream().map(s -> s.getId()).collect(Collectors.toList());
        if (StringUtils.isNullOrEmpty(groupIds)) {
            return new ArrayList<>();
        }
        LambdaQueryWrapper<WorkflowNodeInstanceExt> lq = Wrappers.lambdaQuery();
        lq.eq(WorkflowNodeInstanceExt::getWorkflowInstanceId, workflowInstanceId);
        lq.in(WorkflowNodeInstanceExt::getUserGroupId, groupIds);
        lq.orderByAsc(WorkflowNodeInstanceExt::getCreateTime);

        return workflowNodeInstanceExtMapper.selectList(lq);
    }

    @Override
    public WorkflowNodeInstanceExt queryCurrentTask(String workflowInstanceId, String userId) {
        Optional<WorkflowNodeInstanceExt> current = queryTask(workflowInstanceId, userId).stream().filter(s -> s.getNodePassType() == NodePassTypeEnum.Default).findFirst();
        if (current.isPresent()) {
            return current.get();
        } else {
            return null;
        }
    }

    @Override
    public WorkflowNodeInstanceExt queryCurrentTaskByInstance(String workflowInstanceId) {
        LambdaQueryWrapper<WorkflowNodeInstanceExt> lq = Wrappers.lambdaQuery();
        lq.eq(WorkflowNodeInstanceExt::getWorkflowInstanceId, workflowInstanceId);
        lq.orderByDesc(WorkflowNodeInstanceExt::getCreateTime);
        Optional<WorkflowNodeInstanceExt> ret = workflowNodeInstanceExtMapper.selectList(lq).stream().findFirst();
        if (ret.isPresent()) {
            return ret.get();
        } else {
            return null;
        }
    }

    @Override
    public WorkflowNodeInstanceExt queryTaskById(String instanceNodeId) {
        return workflowNodeInstanceExtMapper.selectById(instanceNodeId);
    }

    @Override
    public List<WorkflowNodeInstanceExt> queryCurrentTask(String userId) {
        List<String> groupIds = userGroupService.queryUserGroupIdByUser(userId);
        if (StringUtils.isNullOrEmpty(groupIds)) {
            return new ArrayList<>();
        }
        return baseMapper.selectExtByUserGroup(groupIds);
    }

    @Override
    public WorkflowInstance queryById(String workflowInstanceId) {
        return baseMapper.selectById(workflowInstanceId);
    }

    @Override
    public void sendPushNotify(String userId, WorkflowNodeInstanceExt node) {
        String id = configureService.getValueByCode(CostCode.workflow_push_template_id);
        String config = configureService.getValueByCode(CostCode.workflow_push_config);
        String url = configureService.getValueByCode(CostCode.workflow_push_url);

        if (StringUtils.isNullOrEmpty(id) || StringUtils.isNullOrEmpty(config)) {
            logger.warn(String.format("流程模板或配置为空，【%s】消息发送取消。", node.getWorkflowName()));
            return;
        }

        wxService.sendTemplateMessage(
                node.getTenantId(),
                node.getWorkflowName(),
                userId,
                id,
                StringUtils.splitTemplate(config.split("~"), node),
                url
        );
    }

    @Override
    public void sendCloseNotify(String userId, String workflowInstanceId, boolean isPass) {
        String id = configureService.getValueByCode(CostCode.workflow_push_template_id);
        String url = configureService.getValueByCode(CostCode.workflow_push_url);

        if (StringUtils.isNullOrEmpty(id)) {
            logger.warn(String.format("流程模板或配置为空，消息发送取消。"));
            return;
        }

        WorkflowInstance instance = baseMapper.selectByIdNotTenant(workflowInstanceId);
        if (instance == null) {
            throw new BusinessEntityNotFoundException(workflowInstanceId, "流程实例");
        }

        wxService.sendTemplateMessage(
                instance.getTenantId(),
                userId,
                id,
                String.format("【%s-%s】流程未通过。", instance.getInventoryNoteCode(), instance.getWorkflowName()),
                instance.getWorkflowName(),
                isPass ? "已通过" : "未通过",
                instance.getInventoryNoteCode(), null, "请登录电脑端完成后续操作。", url)
        ;
    }

    private void sendNotifyByUserGroup(String userGroupId, WorkflowNodeInstanceExt node) {

        if (StringUtils.isNullOrEmpty(userGroupId)) {
            return;
        }
        if (node == null) {
            return;
        }

        for (String userId : userGroupService.queryItem(null, userGroupId, -1, 1).getRecords().stream().map(s -> s.getUserId()).distinct().collect(Collectors.toList())) {
            sendPushNotify(userId, node);
        }

    }
}
